Перейти к основному содержимому

Глава 3. Массивы указателей, строки и многоуровневая адресация

Массивы указателей

Массив указателей — это массив, каждый элемент которого является указателем на другие данные.

int a = 10, b = 20, c = 30;
int *pointers[3] = {&a, &b, &c}; // Массив из 3 указателей

Массивы указателей на строки

#include <stdio.h>

int main() {
char *days[7] = {
"Понедельник",
"Вторник",
"Среда",
"Четверг",
"Пятница",
"Суббота",
"Воскресенье"
};

printf("Дни недели:\n");
for (int i = 0; i < 7; i++) {
printf("%d. %s\n", i + 1, days[i]);
}

// Доступ к отдельным символам
printf("\nПервая буква среды: '%c'\n", *days[2]);
printf("Вторая буква среды: '%c'\n", *(days[2] + 1));

return 0;
}

Указатели на указатели

Переменная, которая хранит адрес другого указателя.

Двойная адресация

#include <stdio.h>

int main() {
int value = 42;
int *ptr = &value; // Указатель на value
int **ptrToPtr = &ptr; // Указатель на указатель

printf("=== УРОВНИ АДРЕСАЦИИ ===\n");
printf("value = %d\n", value);
printf("*ptr = %d\n", *ptr);
printf("**ptrToPtr = %d\n", **ptrToPtr);

printf("\nАдреса:\n");
printf("&value = %p\n", &value);
printf("ptr = %p\n", ptr);
printf("&ptr = %p\n", &ptr);
printf("ptrToPtr = %p\n", ptrToPtr);

return 0;
}

Изменение через двойной указатель

#include <stdio.h>

int main() {
int score = 85;
int *scorePtr = &score;
int **doublePtrScore = &scorePtr;

printf("Исходный счет: %d\n", score);

// Изменяем значение через двойной указатель
**doublePtrScore += 10;

printf("После изменения через **: %d\n", score); // 95

return 0;
}

Практическое применение

Таблица строк

#include <stdio.h>

int main() {
char *employees[4] = {
"Иван Петров",
"Анна Сидорова",
"Петр Козлов",
"Елена Волкова"
};

char *departments[4] = {
"Разработка",
"Маркетинг",
"Продажи",
"HR"
};

printf("=== СПИСОК СОТРУДНИКОВ ===\n");
for (int i = 0; i < 4; i++) {
printf("%-15s - %s\n", employees[i], departments[i]);
}

// Поиск сотрудника
char *searchName = "Анна Сидорова";
for (int i = 0; i < 4; i++) {
char *currentName = employees[i];
char *searchPtr = searchName;
char *namePtr = currentName;
int match = 1;

// Простое сравнение строк
while (*searchPtr != '\0' && *namePtr != '\0') {
if (*searchPtr != *namePtr) {
match = 0;
break;
}
searchPtr++;
namePtr++;
}

if (match && *searchPtr == '\0' && *namePtr == '\0') {
printf("\n✅ %s работает в отделе: %s\n", searchName, departments[i]);
break;
}
}

return 0;
}

Многоуровневая структура данных

#include <stdio.h>

int main() {
// Двумерная структура через массивы указателей
char *subjects[3] = {"Математика", "Физика", "Информатика"};
char *grades[3] = {"Отлично", "Хорошо", "Отлично"};

// Массив указателей на массивы указателей
char **studentData[2] = {subjects, grades};

printf("=== АКАДЕМИЧЕСКИЕ ДАННЫЕ ===\n");
printf("Предметы и оценки:\n");

for (int i = 0; i < 3; i++) {
char *subject = studentData[0][i]; // subjects[i]
char *grade = studentData[1][i]; // grades[i]
printf("%-15s: %s\n", subject, grade);
}

return 0;
}

Динамическое управление строками

Переключение между строками

#include <stdio.h>

int main() {
char *messagesRU[3] = {
"Добро пожаловать",
"Выберите действие",
"До свидания"
};

char *messagesEN[3] = {
"Welcome",
"Choose action",
"Goodbye"
};

int language = 0; // 0 = русский, 1 = английский
char **currentMessages = (language == 0) ? messagesRU : messagesEN;

printf("=== ИНТЕРФЕЙС ===\n");
for (int i = 0; i < 3; i++) {
printf("%s\n", currentMessages[i]);
}

// Переключаем язык
language = 1;
currentMessages = (language == 0) ? messagesRU : messagesEN;

printf("\n=== INTERFACE ===\n");
for (int i = 0; i < 3; i++) {
printf("%s\n", currentMessages[i]);
}

return 0;
}

Функции с массивами указателей

Обработка списка строк

#include <stdio.h>

int findLongestString(char *strings[], int count) {
int maxLength = 0;
int maxIndex = 0;

for (int i = 0; i < count; i++) {
int length = 0;
char *ptr = strings[i];

// Подсчитываем длину строки
while (*ptr != '\0') {
length++;
ptr++;
}

if (length > maxLength) {
maxLength = length;
maxIndex = i;
}
}

return maxIndex;
}

int main() {
char *cities[5] = {
"Москва",
"Санкт-Петербург",
"Новосибирск",
"Нижний Новгород",
"Казань"
};

int longestIndex = findLongestString(cities, 5);

printf("Города:\n");
for (int i = 0; i < 5; i++) {
printf("%d. %s\n", i + 1, cities[i]);
}

printf("\nСамое длинное название: %s\n", cities[longestIndex]);

return 0;
}

Трехуровневая адресация

Указатель на указатель на указатель

#include <stdio.h>

int main() {
int data = 123;
int *level1 = &data; // Первый уровень
int **level2 = &level1; // Второй уровень
int ***level3 = &level2; // Третий уровень

printf("=== МНОГОУРОВНЕВАЯ АДРЕСАЦИЯ ===\n");
printf("data = %d\n", data);
printf("*level1 = %d\n", *level1);
printf("**level2 = %d\n", **level2);
printf("***level3 = %d\n", ***level3);

printf("\nАдреса:\n");
printf("&data = %p\n", &data);
printf("level1 = %p\n", level1);
printf("&level1 = %p\n", &level1);
printf("level2 = %p\n", level2);
printf("&level2 = %p\n", &level2);
printf("level3 = %p\n", level3);

// Изменение через тройной указатель
***level3 = 456;
printf("\nПосле изменения через ***: data = %d\n", data);

return 0;
}

Практический пример: База данных

Структурированные данные

#include <stdio.h>

int main() {
// База данных студентов
char *names[4] = {"Алексей", "Мария", "Дмитрий", "Анна"};
char *groups[4] = {"ИТ-101", "ИТ-102", "ИТ-101", "ИТ-103"};
char *specialties[4] = {"Программирование", "Дизайн", "Программирование", "Тестирование"};

// Массив указателей на массивы данных
char **database[3] = {names, groups, specialties};
char *fieldNames[3] = {"Имя", "Группа", "Специальность"};

printf("=== БАЗА ДАННЫХ СТУДЕНТОВ ===\n");

// Вывод заголовков
for (int field = 0; field < 3; field++) {
printf("%-15s ", fieldNames[field]);
}
printf("\n");
printf("-----------------------------------------------\n");

// Вывод данных студентов
for (int student = 0; student < 4; student++) {
for (int field = 0; field < 3; field++) {
printf("%-15s ", database[field][student]);
}
printf("\n");
}

// Поиск студентов определенной группы
char *targetGroup = "ИТ-101";
printf("\nСтуденты группы %s:\n", targetGroup);

for (int i = 0; i < 4; i++) {
char *studentGroup = database[1][i]; // groups[i]
char *studentName = database[0][i]; // names[i]

// Простое сравнение строк
char *g1 = studentGroup, *g2 = targetGroup;
int match = 1;

while (*g1 != '\0' && *g2 != '\0') {
if (*g1 != *g2) {
match = 0;
break;
}
g1++;
g2++;
}

if (match && *g1 == '\0' && *g2 == '\0') {
printf("- %s\n", studentName);
}
}

return 0;
}

Манипуляция указателями в массивах

Переназначение указателей

#include <stdio.h>

int main() {
char *status1 = "Активен";
char *status2 = "Неактивен";
char *status3 = "Заблокирован";

char *userStatuses[3] = {status1, status1, status2}; // Начальные статусы
char *userNames[3] = {"Алексей", "Мария", "Дмитрий"};

printf("Исходные статусы:\n");
for (int i = 0; i < 3; i++) {
printf("%s: %s\n", userNames[i], userStatuses[i]);
}

// Изменяем статус пользователя
userStatuses[1] = status3; // Мария становится заблокированной

printf("\nПосле изменения:\n");
for (int i = 0; i < 3; i++) {
printf("%s: %s\n", userNames[i], userStatuses[i]);
}

return 0;
}

Функции с многоуровневыми указателями

Обработка таблицы строк

#include <stdio.h>

void printTable(char **rows, int rowCount, char **headers, int colCount) {
// Печатаем заголовки
for (int col = 0; col < colCount; col++) {
printf("%-12s ", headers[col]);
}
printf("\n");

// Печатаем разделитель
for (int col = 0; col < colCount; col++) {
printf("------------ ");
}
printf("\n");

// Печатаем строки данных
for (int row = 0; row < rowCount; row++) {
printf("%-12s ", rows[row]);
}
printf("\n");
}

int main() {
char *productNames[4] = {"Ноутбук", "Мышь", "Клавиатура", "Монитор"};
char *headers[4] = {"Товар", "Статус", "Количество", "Цена"};

printf("Вызов функции с массивом указателей:\n");
printTable(productNames, 4, headers, 4);

return 0;
}

Память и эффективность

Сравнение подходов

#include <stdio.h>

int main() {
// Подход 1: Двумерный массив символов
char countries2D[3][15] = {
"Россия",
"Германия",
"Франция"
};

// Подход 2: Массив указателей на строки
char *countriesPtr[3] = {
"Россия",
"Германия",
"Франция"
};

printf("=== СРАВНЕНИЕ ПАМЯТИ ===\n");
printf("Двумерный массив: %zu байт\n", sizeof(countries2D)); // 45 байт
printf("Массив указателей: %zu байт\n", sizeof(countriesPtr)); // 24 байта

printf("\nОба подхода работают одинаково:\n");
printf("2D массив: %s, %s, %s\n", countries2D[0], countries2D[1], countries2D[2]);
printf("Указатели: %s, %s, %s\n", countriesPtr[0], countriesPtr[1], countriesPtr[2]);

return 0;
}
Ключевые концепции
  • Массив указателей — каждый элемент указывает на отдельные данные
  • Указатель на указатель — двухуровневая адресация
  • Многоуровневая адресация — цепочка указателей для сложных структур
  • Эффективность памяти — массивы указателей экономят место для строк разной длины
Когда использовать
  • Массивы указателей на строки — для списков текста разной длины
  • Указатели на указатели — для изменения самих указателей в функциях
  • Многоуровневую адресацию — для сложных иерархических структур данных

Многоуровневая адресация открывает возможности для создания гибких и эффективных структур данных.